<!DOCTYPE html>
<html lang="en">
	<head>
		<title>three.js webgl - geometry - cube</title>
		<meta charset="utf-8">
		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
		<link type="text/css" rel="stylesheet" href="main.css">
	</head>
	<body>

		<!-- Import maps polyfill -->
		<!-- Remove this when import maps will be widely supported -->
		<!-- <script async src="https://unpkg.com/es-module-shims@1.3.6/dist/es-module-shims.js"></script> -->
        <script type="importmap">
            {
                "imports":{
                    "three":"./three.module.js"
                }
            }
        </script>

		<script type="module">

			import * as THREE from './three.module.js';
			import { OrbitControls } from './jsm/OrbitControls.js';
			import { FullScreenQuad } from './jsm/Pass.js';
			import { CopyShader } from './jsm/CopyShader.js';
			import { GUI } from './jsm/lil-gui.module.min.js';

			const renderer = new THREE.WebGLRenderer();
			const scene = new THREE.Scene();
			const camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );

			const globalUniforms = {
				uPrevDepthTexture: { value: null },
				uReciprocalScreenSize: { value: new THREE.Vector2( 1, 1 ) },
			};
			const layers = []; // renderTarget
			const one = new THREE.DataTexture( new Uint8Array( [ 1, 1, 1, 1 ] ), 1, 1 ); // 准备纹理
			const quad = new FullScreenQuad(
				new THREE.ShaderMaterial( {
					...CopyShader,
					transparent: true,
					depthTest: false,
					depthWrite: false,
				} )
			);
			let depthPeelingScene = new THREE.Scene();

			const AppState = {
				Enabled: true,
				Depth: 3,
			};
            // 这几个函数就是要在尺寸变化时更新对应数据

			function resizeLayers( width, height, pixelRatio ) {

				const w = width * pixelRatio;
				const h = height * pixelRatio;
				globalUniforms.uReciprocalScreenSize.value.set(
					1 / w,
					1 / h
				);

				layers.forEach( ( rt ) => {

					rt.setSize( w, h );
					rt.depthTexture.dispose();
					rt.depthTexture = new THREE.DepthTexture( w, h );

				} );

			}

			function resizeDepth( width, height, pixelRatio, depth ) {

				while ( depth < layers.length ) layers.pop()?.dispose();

				const w = width * pixelRatio;
				const h = height * pixelRatio;
				while ( layers.length < depth )
					layers.push(
						new THREE.WebGLRenderTarget( w, h, {
							depthTexture: new THREE.DepthTexture( w, h ),
						} )
					);

			}
            // 这个操作 应该是一个场景只需要一个 和材质不挂钩， 那就和场景挂钩
			function peelDepth( renderer, camera ) {
                // 这才是和渲染器挂钩的
				const originalRenderTarget = renderer.getRenderTarget();
				const originalAutoClear = renderer.autoClear;
				renderer.autoClear = false;

				layers.reduceRight( ( prevDepth, layer ) => {  // reduce的从右到左版
                    // 拿第一个纹理作为 a  ， 用这个纹理的深度信息去剥离绘制一次，得到新的的纹理， 把这个纹理作为下一次循环的💰一个纹理，有点链表的意思啊
					globalUniforms.uPrevDepthTexture.value = prevDepth;
					renderer.setRenderTarget( layer );
					renderer.clear();
					renderer.render( depthPeelingScene, camera );
					return layer.depthTexture;

				}, one );
                //这样就存了一个数组的纹理，分别剥离了n次

				renderer.setRenderTarget( originalRenderTarget );
				renderer.clear();

				for ( const layer of layers ) {// 之前是倒着剥离的，所以第一个是剥离次数最多，深度最大的
                    // 直接把之前的每次剥离的结果一次次覆盖？  quad 内部实现不知道啊
					quad.material.uniforms.tDiffuse.value =
						layer.texture;
					quad.material.needsUpdate = true;
					quad.render( renderer );

				}

				renderer.autoClear = originalAutoClear;

			}

			init().then( animate );

			async function init() {

				renderer.setPixelRatio( window.devicePixelRatio );
				renderer.setSize( window.innerWidth, window.innerHeight );
				document.body.appendChild( renderer.domElement );

				camera.position.z = 5;

				renderer.shadowMap.enabled = true;

				const dirLight = new THREE.DirectionalLight();
				dirLight.position.set( 0, 3, 0 );
				dirLight.castShadow = true;
				scene.add( dirLight );
				scene.add( new THREE.AmbientLight( undefined, 0.5 ) );

				const sphere = new THREE.Mesh(
					new THREE.SphereBufferGeometry(),
					new THREE.MeshStandardMaterial()
				);
				sphere.translateY( 3 ).translateX( 1.5 );
				sphere.castShadow = true;
				scene.add( sphere );

				const knot = new THREE.Mesh(
					new THREE.TorusKnotBufferGeometry( undefined, undefined, 128, 32 ),
					// 只有扭结使用了这个材质
                    new PbrMat(
                         THREE.MeshStandardMaterial,
                          {
                            transparent: true,
                            opacity: 0.7,
                            side: THREE.DoubleSide,
                        }
                        )
				);
				knot.receiveShadow = true;
				scene.add( knot );

				scene.add(
					new THREE.Mesh(
						new THREE.BoxBufferGeometry(),
						new THREE.MeshBasicMaterial( { color: 0xf0a000 } )
					)
				);

				const texture = await new THREE.TextureLoader().loadAsync(
					'textures/sprite0.png'
				);
				const plane = new THREE.Mesh(
					new THREE.PlaneBufferGeometry( 3, 3 ),
					new THREE.MeshStandardMaterial( { map: texture, side: THREE.DoubleSide } )
				);
				plane.translateX( - 1.6 );
				plane.translateY( 1.5 );
				scene.add( plane );

				const plane2 = new THREE.Mesh(
					new THREE.PlaneBufferGeometry( 3, 3 ),
					new THREE.MeshStandardMaterial( {
						map: texture,
						transparent: true,
						side: THREE.DoubleSide,
					} )
				);
				plane2
					.translateX( - 1.2 )
					.translateY( - 1.5 )
					.translateZ( 0 )
					.rotateY( - 2 * Math.PI * ( 1 / 10 ) );
				scene.add( plane2 );

				depthPeelingScene = scene.clone( true );// 克隆原本的场景 是为了区别开启和关闭
                // 
				resizeLayers( window.innerWidth, window.innerHeight, renderer.getPixelRatio() );
				resizeDepth( window.innerWidth, window.innerHeight, renderer.getPixelRatio(), AppState.Depth );
                //
/*				depthPeelingScene.traverse( ( obj ) => {

					if ( obj instanceof THREE.Mesh && obj.material instanceof THREE.Material ) {

						const clonedMaterial = obj.material.clone();
						clonedMaterial.blending = THREE.NoBlending;
						clonedMaterial.onBeforeCompile = ( shader ) => {
							shader.uniforms.uReciprocalScreenSize = globalUniforms.uReciprocalScreenSize;
							shader.uniforms.uPrevDepthTexture = globalUniforms.uPrevDepthTexture;
							shader.fragmentShader = `
// --- DEPTH PEELING SHADER CHUNK (START) (uniform definition)
uniform vec2 uReciprocalScreenSize;
uniform sampler2D uPrevDepthTexture;
// --- DEPTH PEELING SHADER CHUNK (END)
					${shader.fragmentShader}
				`;
							//peel depth
							shader.fragmentShader = shader.fragmentShader.replace(
								// /}$/gm,
                                /void ?main\(\) ?\{/,
								`
// --- DEPTH PEELING SHADER CHUNK (START) (peeling)
    void main(){
  vec2 screenPos = gl_FragCoord.xy * uReciprocalScreenSize;
  float prevDepth = texture2D(uPrevDepthTexture,screenPos).x;
  if( prevDepth >= gl_FragCoord.z )
      discard;
// --- DEPTH PEELING SHADER CHUNK (END)

					`
							);

						};

						obj.material = clonedMaterial;
						obj.material.needsUpdate = true;

					}

				} );
*/


/**
 *
 * @param {Material} Mat
 * @param {*} parameter 初始化材质的参数
 * 
 */
 function PbrMat(Mat = MeshPhysicalMaterial,parameter  ) {
            const mat = new Mat(parameter);
    mat.depthPeeling = true;
     // 这些东西就挂在场景上了  视口就手动更新，渲染前判断一下，缺点是要传个场景参数

    mat.blending = THREE.NoBlending;
	mat.onBeforeCompile = ( shader ) => {
	shader.uniforms.uReciprocalScreenSize = globalUniforms.uReciprocalScreenSize;
	shader.uniforms.uPrevDepthTexture = globalUniforms.uPrevDepthTexture;
	shader.fragmentShader = `
// --- DEPTH PEELING SHADER CHUNK (START) (uniform definition)
uniform vec2 uReciprocalScreenSize;
uniform sampler2D uPrevDepthTexture;
// --- DEPTH PEELING SHADER CHUNK (END)
					${shader.fragmentShader}
				`;


 //peel depth

shader.fragmentShader = shader.fragmentShader.replace(
								// /}$/gm,
    /void ?main\(\) ?\{/,
								`
// --- DEPTH PEELING SHADER CHUNK (START) (peeling)
    void main(){
  vec2 screenPos = gl_FragCoord.xy * uReciprocalScreenSize;
  float prevDepth = texture2D(uPrevDepthTexture,screenPos).x;
  if( prevDepth >= gl_FragCoord.z )
      discard;
// --- DEPTH PEELING SHADER CHUNK (END)

					`
	);
    };

    return mat;



}


				const orbit = new OrbitControls( camera, renderer.domElement );
				orbit.update();
				orbit.addEventListener( 'change', animate );

				const gui = new GUI();
				gui.add( AppState, 'Enabled' ).onChange( animate );
				gui.add( AppState, 'Depth' ).min( 1 ).max( 5 ).step( 1 ).onChange( v => {

					resizeDepth( window.innerWidth, window.innerHeight, renderer.getPixelRatio(), v );
					animate();

				} );

				window.addEventListener( 'resize', () => {

					camera.aspect = window.innerWidth / window.innerHeight;
					camera.updateProjectionMatrix();

					renderer.setSize( window.innerWidth, window.innerHeight );
					resizeLayers( window.innerWidth, window.innerHeight, renderer.getPixelRatio() );
					animate();

				} );

			}

			function animate() {

				requestAnimationFrame( () =>
					AppState.Enabled ? peelDepth( renderer, camera ) : renderer.render( scene, camera )
				);

			}
	</script>
	</body>
</html>
